{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "15750915",
   "metadata": {},
   "source": [
    "| [04_text_classification/04_应用_姓名识别国籍.ipynb](https://github.com/shibing624/nlp-tutorial/tree/main/04_text_classification/04_应用_姓名识别国籍.ipynb)  | 从头训练RNN模型做人名的国籍分类  |[Open In Colab](https://colab.research.google.com/github/shibing624/nlp-tutorial/blob/main/04_text_classification/04_应用_姓名识别国籍.ipynb) |\n",
    "\n",
    "\n",
    "# 用字符级RNN分类名称\n",
    "\n",
    "**作者**: [Sean Robertson](https://github.com/spro/practical-pytorch)\n",
    "\n",
    "我们将建立和训练一个基本的字符级RNN进行分类单词. 字符级别的RNN将单词读为一系列字符 - 在每个步骤输出一个预测和“隐藏状态”,\n",
    "将先前的隐藏状态作为下一步的输入. 我们采取最后的预测作为输出,即该单词属于哪一类.\n",
    "\n",
    "具体来说,我们将用18种语言的几千个姓氏作为训练集并根据拼写预测名称来自哪种语言，哪个国籍\n",
    "\n",
    "::\n",
    "\n",
    "    $ python predict.py Hinton\n",
    "    (-0.47) Scottish\n",
    "    (-1.52) English\n",
    "    (-3.57) Irish\n",
    "\n",
    "    $ python predict.py Schmidhuber\n",
    "    (-0.19) German\n",
    "    (-2.48) Czech\n",
    "    (-2.68) Dutch\n",
    "\n",
    "\n",
    "**推荐阅读:**\n",
    "\n",
    "假设你至少已经安装了PyTorch,知道Python和了解张量:\n",
    "\n",
    "-  http://pytorch.org/ 安装步骤\n",
    "-  :doc:`/beginner/deep_learning_60min_blitz` 大体了解PyTorch\n",
    "-  :doc:`/beginner/pytorch_with_examples` 深入概括\n",
    "-  :doc:`/beginner/former_torchies_tutorial` 假设你是前Lua Torch用户\n",
    "\n",
    "了解RNN及其工作方式也很有用:\n",
    "\n",
    "-  `递归神经网络的不合理有效性 <http://karpathy.github.io/2015/05/21/rnn-effectiveness/>`__\n",
    "   展示了一堆真实生活的例子\n",
    "-  `理解LSTM网络 <http://colah.github.io/posts/2015-08-Understanding-LSTMs/>`__\n",
    "   是关于LSTM的具体内容,但也包含有关RNN的一般信息\n",
    "\n",
    "## 准备数据\n",
    "\n",
    "\n",
    ".. 注意::\n",
    "   从这里下载数据\n",
    "   `here <https://download.pytorch.org/tutorial/data.zip>`_\n",
    "   并将其解压到当前目录.（本教程已经下载过了）\n",
    "\n",
    "在 ``data/names`` 目录中包含18个名为as的文本文件 \"[Language].txt\" .\n",
    "每个文件都包含一堆名称,每个名称一行大多是罗马化（但我们仍然需要从Unicode转换为ASCII）.\n",
    "\n",
    "我们最终会得到每种语言的名称列表字典 ``{language: [names ...]}``  通用变量“类别”和“行”\n",
    "（在我们的例子中用于语言和名称）用于以后的扩展性。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c697fabe",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['data/names/Czech.txt', 'data/names/German.txt', 'data/names/Arabic.txt', 'data/names/Japanese.txt', 'data/names/Chinese.txt', 'data/names/Vietnamese.txt', 'data/names/Russian.txt', 'data/names/French.txt', 'data/names/Irish.txt', 'data/names/English.txt', 'data/names/Spanish.txt', 'data/names/Greek.txt', 'data/names/Italian.txt', 'data/names/Portuguese.txt', 'data/names/Scottish.txt', 'data/names/Dutch.txt', 'data/names/Korean.txt', 'data/names/Polish.txt']\n",
      "Slusarski\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "18"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from io import open\n",
    "import glob\n",
    "\n",
    "def findFiles(path): return glob.glob(path)\n",
    "\n",
    "print(findFiles('data/names/*.txt'))\n",
    "\n",
    "import unicodedata\n",
    "import string\n",
    "\n",
    "all_letters = string.ascii_letters + \" .,;'\"\n",
    "n_letters = len(all_letters)\n",
    "\n",
    "# 将 Unicode 字符串转换为纯 ASCII 编码, 这里感谢 http://stackoverflow.com/a/518232/2809427\n",
    "def unicodeToAscii(s):\n",
    "    return ''.join(\n",
    "        c for c in unicodedata.normalize('NFD', s)\n",
    "        if unicodedata.category(c) != 'Mn'\n",
    "        and c in all_letters\n",
    "    )\n",
    "\n",
    "print(unicodeToAscii('Ślusàrski'))\n",
    "\n",
    "# 构建category_lines字典, 每种语言的名称列表\n",
    "category_lines = {}\n",
    "all_categories = []\n",
    "\n",
    "# 读取一个文件并分成几行\n",
    "def readLines(filename):\n",
    "    lines = open(filename, encoding='utf-8').read().strip().split('\\n')\n",
    "    return [unicodeToAscii(line) for line in lines]\n",
    "\n",
    "for filename in findFiles('data/names/*.txt'):\n",
    "    category = filename.split('/')[-1].split('.')[0]\n",
    "    all_categories.append(category)\n",
    "    lines = readLines(filename)\n",
    "    category_lines[category] = lines\n",
    "\n",
    "n_categories = len(all_categories)\n",
    "n_categories"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bd3f16d",
   "metadata": {},
   "source": [
    "现在我们有 ``category_lines``, 这是一个映射每个类别的字典\n",
    "(语言) 到行列表 (名称). 我们也跟踪\n",
    "``all_categories`` (只是一个语言列表) 和 ``n_categories`` 为以后做参考.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "409e3102",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Abandonato', 'Abatangelo', 'Abatantuono', 'Abate', 'Abategiovanni']\n"
     ]
    }
   ],
   "source": [
    "print(category_lines['Italian'][:5])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b436331",
   "metadata": {},
   "source": [
    "将名字转化为张量\n",
    "--------------------------\n",
    "\n",
    "现在我们已经组织了所有的名字,我们需要将它们变成张量以便使用它们.\n",
    "\n",
    "为了表示单个字母,我们使用大小为 ``<1 x n_letters>`` 的\"单热矢量\".\n",
    "除了当前字母的索引处的1以外,单热矢量剩余填充0, e.g. ``\"b\" = <0 1 0 0 0 ...>``.\n",
    "\n",
    "为了说出一个词,我们将其中的一部分加入到二维矩阵中\n",
    "``<line_length x 1 x n_letters>``.\n",
    "\n",
    "额外的1维度是因为PyTorch假定所有内容都是批量的 - 我们在这里只使用1的批量大小.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "204827b6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n",
      "         0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.,\n",
      "         0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n",
      "         0., 0., 0.]])\n",
      "torch.Size([5, 1, 57])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "\n",
    "# 从all_letters中查找字母索引, e.g. \"a\" = 0\n",
    "def letterToIndex(letter):\n",
    "    return all_letters.find(letter)\n",
    "\n",
    "# 只是为了演示, 把一个字母变成一个 <1 x n_letters> 张量\n",
    "def letterToTensor(letter):\n",
    "    tensor = torch.zeros(1, n_letters)\n",
    "    tensor[0][letterToIndex(letter)] = 1\n",
    "    return tensor\n",
    "\n",
    "# 把一行变成一个 <line_length x 1 x n_letters>,\n",
    "# 或一批单热字符向量\n",
    "def lineToTensor(line):\n",
    "    tensor = torch.zeros(len(line), 1, n_letters)\n",
    "    for li, letter in enumerate(line):\n",
    "        tensor[li][0][letterToIndex(letter)] = 1\n",
    "    return tensor\n",
    "\n",
    "print(letterToTensor('J'))\n",
    "\n",
    "print(lineToTensor('Jones').size())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a76e850c",
   "metadata": {},
   "source": [
    "创建网络\n",
    "====================\n",
    "\n",
    "在autograd之前, 在Torch中创建一个循环神经网络涉及到克隆几个步骤一个图层的参数.\n",
    "图层保持隐藏状态和渐变, 现在完全由图形本身处理.\n",
    "这意味着您可以以非常“纯粹”的方式实现RNN, 作为常规的前馈层.\n",
    "\n",
    "这个RNN模块 (大部分都是复制 `the PyTorch for Torch users tutorial <http://pytorch.org/tutorials/beginner/former_torchies/nn_tutorial.html#example-2-recurrent-net>`__)\n",
    "只有2个线性层可以在输入和隐藏状态下运行, 在输出之后有一个LogSoftmax层.\n",
    "\n",
    ".. figure:: https://i.imgur.com/Z2xbySO.png\n",
    "   :alt:\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1b254141",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "from torch.autograd import Variable\n",
    "\n",
    "class RNN(nn.Module):\n",
    "    def __init__(self, input_size, hidden_size, output_size):\n",
    "        super(RNN, self).__init__()\n",
    "\n",
    "        self.hidden_size = hidden_size\n",
    "\n",
    "        self.i2h = nn.Linear(input_size + hidden_size, hidden_size)\n",
    "        self.i2o = nn.Linear(input_size + hidden_size, output_size)\n",
    "        self.softmax = nn.LogSoftmax(dim=1)\n",
    "\n",
    "    def forward(self, input, hidden):\n",
    "        combined = torch.cat((input, hidden), 1)\n",
    "        hidden = self.i2h(combined)\n",
    "        output = self.i2o(combined)\n",
    "        output = self.softmax(output)\n",
    "        return output, hidden\n",
    "\n",
    "    def initHidden(self):\n",
    "        return Variable(torch.zeros(1, self.hidden_size))\n",
    "\n",
    "n_hidden = 128\n",
    "rnn = RNN(n_letters, n_hidden, n_categories)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89fcb93b",
   "metadata": {},
   "source": [
    "为了运行这个网络的一个步骤, 我们需要传递一个输入 (在我们的例子中是当前字母的张量) 和一个先前的隐藏状态 (我们首先初始化为零) .\n",
    "我们将返回输出 (每种语言的概率) 和下一个隐藏状态 (我们为下一步保留).\n",
    "请记住, PyTorch模块对变量进行操作, 而不是直接对张量进行操作.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7a0264c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "input = Variable(letterToTensor('A'))\n",
    "hidden = Variable(torch.zeros(1, n_hidden))\n",
    "\n",
    "output, next_hidden = rnn(input, hidden)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c03c791",
   "metadata": {},
   "source": [
    "为了提高效率我们不希望为每一步创建一个新的张量,\n",
    "所以我们使用 ``lineToTensor`` 而不是 ``letterToTensor`` 并使用切片.\n",
    "这可以通过预先计算批次的张量进一步优化.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d057b131",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-2.9033, -2.8689, -2.9827, -2.8651, -2.8183, -2.8682, -3.0024, -2.8601,\n",
      "         -2.9306, -2.8515, -2.9285, -2.8694, -2.8406, -2.8191, -2.9180, -2.8976,\n",
      "         -3.0155, -2.8181]], grad_fn=<LogSoftmaxBackward>)\n"
     ]
    }
   ],
   "source": [
    "input = Variable(lineToTensor('Albert'))\n",
    "hidden = Variable(torch.zeros(1, n_hidden))\n",
    "\n",
    "output, next_hidden = rnn(input[0], hidden)\n",
    "print(output)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c76d85d8",
   "metadata": {},
   "source": [
    "正如你所看到的输出是一个 ``<1 x n_categories>`` 张量,\n",
    "每个项目都是该类别的可能性 (越高越有可能).\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c421394",
   "metadata": {},
   "source": [
    "训练\n",
    "========\n",
    "准备训练\n",
    "----------------------\n",
    "\n",
    "在训练之前,我们应该做一些辅助功能.\n",
    "首先是解释网络的输出, 我们知道这是每个类别的可能性.\n",
    "我么可以使用 ``Tensor.topk`` 得到最大价值的指数:\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "60be6aa1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('Polish', tensor(17))\n"
     ]
    }
   ],
   "source": [
    "def categoryFromOutput(output):\n",
    "    top_n, top_i = output.data.topk(1) # Tensor out of Variable with .data\n",
    "    category_i = top_i[0][0]\n",
    "    return all_categories[category_i], category_i\n",
    "\n",
    "print(categoryFromOutput(output))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82769c2d",
   "metadata": {},
   "source": [
    "我们也希望能够快速获得训练示例 (名称及其语言):\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "d0976d93",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "category = Greek / line = Koulaxizis\n",
      "category = Italian / line = Romagnoli\n",
      "category = English / line = Ewles\n",
      "category = French / line = Marion\n",
      "category = Spanish / line = Vega\n",
      "category = Arabic / line = Wasem\n",
      "category = Italian / line = Arrighi\n",
      "category = French / line = Belanger\n",
      "category = Irish / line = Conchobhar\n",
      "category = Irish / line = Raghailligh\n"
     ]
    }
   ],
   "source": [
    "import random\n",
    "\n",
    "def randomChoice(l):\n",
    "    return l[random.randint(0, len(l) - 1)]\n",
    "\n",
    "def randomTrainingExample():\n",
    "    category = randomChoice(all_categories)\n",
    "    line = randomChoice(category_lines[category])\n",
    "    category_tensor = Variable(torch.LongTensor([all_categories.index(category)]))\n",
    "    line_tensor = Variable(lineToTensor(line))\n",
    "    return category, line, category_tensor, line_tensor\n",
    "\n",
    "for i in range(10):\n",
    "    category, line, category_tensor, line_tensor = randomTrainingExample()\n",
    "    print('category =', category, '/ line =', line)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4530a72",
   "metadata": {},
   "source": [
    "训练网络\n",
    "--------------------\n",
    "\n",
    "现在训练这个网络所需要的就是向大家展示一些例子, 让它猜测, 并告诉它是否是错误的.\n",
    "\n",
    "对于损失函数 ``nn.NLLLoss`` 是适当的, 因为RNN的最后一层是 ``nn.LogSoftmax``.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "79d3743f",
   "metadata": {},
   "outputs": [],
   "source": [
    "criterion = nn.NLLLoss()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b40912f4",
   "metadata": {},
   "source": [
    "每个训练循环都会:\n",
    "\n",
    "-  创建输入和目标张量\n",
    "-  创建一个归零的初始隐藏状态\n",
    "-  读入每个字母\n",
    "\n",
    "   -  为下一个字母保持隐藏状态\n",
    "\n",
    "-  比较最终输出与目标\n",
    "-  反向传播\n",
    "-  返回输出和损失\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f52b8737",
   "metadata": {},
   "outputs": [],
   "source": [
    "learning_rate = 0.005 # 如果设置得太高, 可能会爆炸. 如果太低, 可能无法学习.\n",
    "\n",
    "def train(category_tensor, line_tensor):\n",
    "    hidden = rnn.initHidden()\n",
    "\n",
    "    rnn.zero_grad()\n",
    "\n",
    "    for i in range(line_tensor.size()[0]):\n",
    "        output, hidden = rnn(line_tensor[i], hidden)\n",
    "\n",
    "    loss = criterion(output, category_tensor)\n",
    "    loss.backward()\n",
    "\n",
    "    # 将参数梯度添加到它们的值,再乘以学习速率\n",
    "    for p in rnn.parameters():\n",
    "        p.data.add_(-learning_rate, p.grad.data)\n",
    "\n",
    "    return output, loss.item()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a63152d8",
   "metadata": {},
   "source": [
    "现在我们只需要运行一些例子. 由于 ``train`` 函数返回输出和损失,我们可以打印它的猜测,并记录绘图的损失\n",
    "既然有1000个例子, 我们只打印每个 ``print_every`` 的例子, 并取平均的损失.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b413153f",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-10-c35096730220>:16: UserWarning: This overload of add_ is deprecated:\n",
      "\tadd_(Number alpha, Tensor other)\n",
      "Consider using one of the following signatures instead:\n",
      "\tadd_(Tensor other, *, Number alpha) (Triggered internally at  ../torch/csrc/utils/python_arg_parser.cpp:1025.)\n",
      "  p.data.add_(-learning_rate, p.grad.data)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "iter: 5000 6% (0m 3s) 2.4320 Reier / German ✓\n",
      "iter: 10000 13% (0m 7s) 2.5692 Walentowicz / German ✗ (Polish)\n",
      "iter: 15000 20% (0m 11s) 0.8628 Mancini / Italian ✓\n",
      "iter: 20000 26% (0m 17s) 2.2693 Ngai / Chinese ✗ (Korean)\n",
      "iter: 25000 33% (0m 22s) 1.8069 Trukhin / Arabic ✗ (Russian)\n",
      "iter: 30000 40% (0m 26s) 2.9036 Koning / Italian ✗ (Dutch)\n",
      "iter: 35000 46% (0m 32s) 0.3137 Bobienski / Polish ✓\n",
      "iter: 40000 53% (0m 37s) 0.0070 Warszawski / Polish ✓\n",
      "iter: 45000 60% (0m 42s) 0.3850 Yuan / Chinese ✓\n",
      "iter: 50000 66% (0m 47s) 1.9118 Koliha / Japanese ✗ (Czech)\n",
      "iter: 55000 73% (0m 54s) 2.8273 Close / French ✗ (Greek)\n",
      "iter: 60000 80% (0m 59s) 3.8372 Colon / Scottish ✗ (Spanish)\n",
      "iter: 65000 86% (1m 3s) 1.6648 Monet / French ✓\n",
      "iter: 70000 93% (1m 7s) 1.5752 Moshin / Irish ✗ (Russian)\n",
      "iter: 75000 100% (1m 11s) 1.0207 Lagana / Italian ✓\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import math\n",
    "\n",
    "n_iters = 75000\n",
    "print_every = 5000\n",
    "plot_every = 1000\n",
    "\n",
    "# 跟踪绘图的损失\n",
    "current_loss = 0\n",
    "all_losses = []\n",
    "\n",
    "def timeSince(since):\n",
    "    now = time.time()\n",
    "    s = now - since\n",
    "    m = math.floor(s / 60)\n",
    "    s -= m * 60\n",
    "    return '%dm %ds' % (m, s)\n",
    "\n",
    "start = time.time()\n",
    "\n",
    "for iter in range(1, n_iters + 1):\n",
    "    category, line, category_tensor, line_tensor = randomTrainingExample()\n",
    "    output, loss = train(category_tensor, line_tensor)\n",
    "    current_loss += loss\n",
    "\n",
    "    # 打印循环数,损失,名称和猜测\n",
    "    if iter % print_every == 0:\n",
    "        guess, guess_i = categoryFromOutput(output)\n",
    "        correct = '✓' if guess == category else '✗ (%s)' % category\n",
    "        print('iter: %d %d%% (%s) %.4f %s / %s %s' % (iter, iter / n_iters * 100, timeSince(start), loss, line, guess, correct))\n",
    "\n",
    "    # 将当前损失平均值添加到损失清单\n",
    "    if iter % plot_every == 0:\n",
    "        all_losses.append(current_loss / plot_every)\n",
    "        current_loss = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "af7cf05e",
   "metadata": {},
   "source": [
    "绘制结果\n",
    "--------------------\n",
    "\n",
    "从 ``all_losses`` 绘制历史损失显示网络学习:\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "48068a4c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7f81c8a35f10>]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.ticker as ticker\n",
    "\n",
    "plt.figure()\n",
    "plt.plot(all_losses)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "532acdf3",
   "metadata": {},
   "source": [
    "评估结果\n",
    "======================\n",
    "\n",
    "要查看网络在不同类别中的表现如何, 我们将创建一个混淆矩阵,\n",
    "为每个实际语言 (行) 指示网络猜测哪种语言 (列).\n",
    "为了计算混淆矩阵,一堆样本通过网络运行 ``evaluate()``,\n",
    "这和 ``train()`` 减去反向传播是一样的.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "a6b3b609",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-13-79c424747e07>:33: UserWarning: FixedFormatter should only be used together with FixedLocator\n",
      "  ax.set_xticklabels([''] + all_categories, rotation=90)\n",
      "<ipython-input-13-79c424747e07>:34: UserWarning: FixedFormatter should only be used together with FixedLocator\n",
      "  ax.set_yticklabels([''] + all_categories)\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 在混淆矩阵中跟踪正确的猜测\n",
    "confusion = torch.zeros(n_categories, n_categories)\n",
    "n_confusion = 10000\n",
    "\n",
    "# 只要返回给定一行的输出即可\n",
    "def evaluate(line_tensor):\n",
    "    hidden = rnn.initHidden()\n",
    "\n",
    "    for i in range(line_tensor.size()[0]):\n",
    "        output, hidden = rnn(line_tensor[i], hidden)\n",
    "\n",
    "    return output\n",
    "\n",
    "# 通过一堆示例并记录哪些是正确的猜测\n",
    "for i in range(n_confusion):\n",
    "    category, line, category_tensor, line_tensor = randomTrainingExample()\n",
    "    output = evaluate(line_tensor)\n",
    "    guess, guess_i = categoryFromOutput(output)\n",
    "    category_i = all_categories.index(category)\n",
    "    confusion[category_i][guess_i] += 1\n",
    "\n",
    "# 通过将每一行除以其总和来标准化\n",
    "for i in range(n_categories):\n",
    "    confusion[i] = confusion[i] / confusion[i].sum()\n",
    "\n",
    "# 设置绘图\n",
    "fig = plt.figure()\n",
    "ax = fig.add_subplot(111)\n",
    "cax = ax.matshow(confusion.numpy())\n",
    "fig.colorbar(cax)\n",
    "\n",
    "# 设置轴\n",
    "ax.set_xticklabels([''] + all_categories, rotation=90)\n",
    "ax.set_yticklabels([''] + all_categories)\n",
    "\n",
    "# Force label at every tick\n",
    "ax.xaxis.set_major_locator(ticker.MultipleLocator(1))\n",
    "ax.yaxis.set_major_locator(ticker.MultipleLocator(1))\n",
    "\n",
    "# sphinx_gallery_thumbnail_number = 2\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8dfd2fda",
   "metadata": {},
   "source": [
    "您可以从主轴上选取显示错误猜测哪些语言的亮点,\n",
    "e.g. Chinese for Korean, and Spanish for Italian.\n",
    "它似乎与希腊语很好,英语很差 (可能是因为与其他语言重叠).\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfa458d1",
   "metadata": {},
   "source": [
    "在用户输入上运行\n",
    "---------------------\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "fa2b76dd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "> Dovesky\n",
      "(-0.38) Russian\n",
      "(-2.10) Czech\n",
      "(-3.02) French\n",
      "\n",
      "> Jackson\n",
      "(-0.76) Scottish\n",
      "(-1.63) Russian\n",
      "(-2.04) English\n",
      "\n",
      "> Satoshi\n",
      "(-0.46) Japanese\n",
      "(-1.89) Italian\n",
      "(-2.24) Arabic\n",
      "\n",
      "> Zhang\n",
      "(-0.36) Chinese\n",
      "(-1.53) Vietnamese\n",
      "(-3.05) Korean\n"
     ]
    }
   ],
   "source": [
    "def predict(input_line, n_predictions=3):\n",
    "    print('\\n> %s' % input_line)\n",
    "    output = evaluate(Variable(lineToTensor(input_line)))\n",
    "\n",
    "    # 获取前N个类别\n",
    "    topv, topi = output.data.topk(n_predictions, 1, True)\n",
    "    predictions = []\n",
    "\n",
    "    for i in range(n_predictions):\n",
    "        value = topv[0][i]\n",
    "        category_index = topi[0][i]\n",
    "        print('(%.2f) %s' % (value, all_categories[category_index]))\n",
    "        predictions.append([value, all_categories[category_index]])\n",
    "\n",
    "predict('Dovesky')\n",
    "predict('Jackson')\n",
    "predict('Satoshi')\n",
    "predict('Zhang')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55c5bdc9",
   "metadata": {},
   "source": [
    "脚本的最终版本 `in the Practical PyTorch repo <https://github.com/spro/practical-pytorch/tree/master/char-rnn-classification>`__\n",
    "将上面的代码分成几个文件:\n",
    "\n",
    "-  ``data.py`` (加载文件)\n",
    "-  ``model.py`` (定义RNN)\n",
    "-  ``train.py`` (运行训练)\n",
    "-  ``predict.py`` (用命令行参数运行 ``predict()`` )\n",
    "-  ``server.py`` (使用bottle.py将预测用作JSON API)\n",
    "\n",
    "运行 ``train.py`` 来训练和保存网络.\n",
    "\n",
    "运行具有名称的 ``predict.py`` 来查看预测:\n",
    "\n",
    "::\n",
    "\n",
    "    $ python predict.py Hazaki\n",
    "    (-0.42) Japanese\n",
    "    (-1.39) Polish\n",
    "    (-3.51) Czech\n",
    "\n",
    "运行 ``server.py`` 和查看 http://localhost:5533/Yourname 获取预测的JSON输出.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14d8298f",
   "metadata": {},
   "source": [
    "练习\n",
    "=========\n",
    "\n",
    "-  尝试使用不同的数据集 线条 -> 类别, 例如:\n",
    "\n",
    "   -  任何单词 -> 语言\n",
    "   -  姓 -> 性别\n",
    "   -  角色名字 -> 作家\n",
    "   -  页面标题 -> 博客或subreddit\n",
    "\n",
    "-  通过更大和/或更好的形状网络获得更好的结果\n",
    "\n",
    "   -  添加更多线性图层\n",
    "   -  试试 ``nn.LSTM`` 和 ``nn.GRU`` 图层\n",
    "   -  将多个这些RNN组合为更高级别的网络\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be1cedbd",
   "metadata": {},
   "source": [
    "本节完。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4d5e5f2",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}